import csv
import struct
import json
import os
import datetime
import tempfile
from shutil import rmtree
from typing import Iterator
from xml.dom import minidom

# subclass JSONEncoder
class DateTimeEncoder(json.JSONEncoder):
    # Override the default method
    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return obj.isoformat()


class FileUtil:
    @staticmethod
    def get_path(file_path: str) -> str:
        return os.path.join(os.getcwd(), file_path)

    @staticmethod
    def join(__path: str, *paths: str) -> str:
        return os.path.join(__path, *paths)

    @staticmethod
    def join_from_parent_dir(current: str, *paths: str) -> str:
        return os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(current)), *paths))

    @staticmethod
    def exists(file_path: str) -> bool:
        return os.path.exists(file_path)

    @staticmethod
    def isfile(file_path: str) -> bool:
        return os.path.isfile(file_path)

    @staticmethod
    def get_files(dir_path: str, ext: str = "") -> list[str]:
        res = []
        for (dir_path, _, file_names) in os.walk(dir_path):
            for f in file_names:
                if ext:
                    if f.lower().endswith(ext):
                        res.append(f"{dir_path}/{f}")
                else:
                    res.append(f"{dir_path}/{f}")

    @staticmethod
    def get_files_iter(base_dir: str, ext: str = "") -> Iterator[str]:
        for (dir_path, _, file_names) in os.walk(base_dir):
            for f in file_names:
                if ext:
                    if f.lower().endswith(ext):
                        yield os.path.join(dir_path, f)
                else:
                    yield os.path.join(dir_path, f)

    @staticmethod
    def make_parent(file_path: str):
        dir_path = os.path.dirname(file_path)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)

    @staticmethod
    def remove_dir(dirname: str):
        rmtree(dirname, ignore_errors=False)

    @staticmethod
    def close_file(fileNo: int):
        os.close(fileNo)

    @staticmethod
    def read_text_file(file_path: str, encoding: str = "utf8") -> str:
        with open(file_path, "r", encoding=encoding, errors="backslashreplace") as text_file:
            data = text_file.read()

        return data

    @staticmethod
    def read_text_file_lines(fileName: str) -> list[str]:
        with open(fileName, "r", encoding="utf-8", errors="backslashreplace") as text_file:
            data = text_file.readlines()
            return data

    @staticmethod
    def write_text_file(file_path: str, content: str, encoding: str = "utf8"):
        FileUtil.make_parent(file_path)
        with open(file_path, "w", encoding=encoding, errors="backslashreplace") as text_file:
            text_file.write(content)

    @staticmethod
    def append_text_file(file_path: str, content: str, encoding: str = "utf8"):
        FileUtil.make_parent(file_path)
        with open(
            file_path,
            "a",
            encoding=encoding,
        ) as text_file:
            text_file.write(content)

    @staticmethod
    def read_json_file(file_path: str, encoding: str = "utf8") -> any:
        with open(file_path, "r", encoding=encoding, errors="backslashreplace") as text_file:
            data = json.load(text_file)

        return data

    @staticmethod
    def write_json_file(file_path: str, content: dict, encoding: str = "utf8"):
        FileUtil.make_parent(file_path)
        with open(file_path, "w", encoding=encoding, errors="backslashreplace") as text_file:
            json.dump(
                content,
                text_file,
                indent=2,
                ensure_ascii=False,
                cls=DateTimeEncoder,
            )

    @staticmethod
    def read_bin_file(file_name: str) -> bytearray:
        content = bytearray()
        with open(file_name, 'rb') as bin_file:
            while True:
                b = bin_file.read(1)
                if not b:
                    break
                content += b
        return content

    @staticmethod
    def read_bin_file_to_floats(bin: str, linesize: int = 3) -> list[tuple[float]]:
        result: list[tuple[float]] = []
        with open(bin, "rb") as bin_file:
            while True:
                b = bin_file.read(4 * linesize)
                if not b:
                    break
                result.append(struct.unpack(f"{linesize}f", b))
        return result

    @staticmethod
    def write_bin_file(file_name: str, content: bytearray):
        with open(file_name, "wb") as bin_file:
            bin_file.write(content)

    @staticmethod
    def create_temp_dir(prefix: str) -> str:
        return tempfile.mkdtemp(prefix=prefix)

    @staticmethod
    def get_temp_file(suffix=None, prefix=None, dir=None, text=False) -> tuple[int, str]:
        return tempfile.mkstemp(suffix, prefix, dir, text)

    @staticmethod
    def read_csv_file(
        file_path: str, newline: str = '', delimiter: str = '\t'
    ) -> list[list[float]]:
        data: list[list[float]] = []
        with open(file_path, 'r', newline=newline) as f_in:
            reader = csv.reader(f_in, delimiter=delimiter)
            for row in reader:
                cells: list[float] = []
                for cell in row:
                    cells.append(float(cell))
                data.append(cells)
        return data

    @staticmethod
    def write_csv_file(file_path: str, content: list[list[any]], newline: str = ''):
        FileUtil.make_parent(file_path)
        with open(file_path, 'w', newline=newline) as f:
            writer = csv.writer(f)
            for row in content:
                writer.writerow(row)

    @staticmethod
    def read_xml_file(file_name: str) -> dict:
        content = {}
        doc: minidom.Document = minidom.parse(file_name)

        # 得到文档元素对象
        FileUtil.fill_xml_element(doc.documentElement, content)
        return content

    @staticmethod
    def fill_xml_element(element: minidom.Element, parent: dict):
        is_list: bool = False
        parent_name = element.nodeName

        # 判断这个元素是不是List
        # 如果元素的孩子名 = 元素名 + s，这个元素是List
        # 如果元素有多个孩子名，且名字相同，这个元素是List
        if element.childNodes:
            first_name = ""
            second_name = ""
            child: minidom.Node
            for child in element.childNodes:
                if child.nodeType == minidom.Node.ELEMENT_NODE:
                    if first_name == "":
                        first_name = child.nodeName
                    else:
                        second_name = child.nodeName
                        break

            if first_name + "s" == parent_name:
                is_list = True
            elif len(second_name) > 0 and first_name == second_name:
                is_list = True

        if is_list:
            items = []
            parent[parent_name] = items
            child: minidom.Node
            for child in element.childNodes:
                if child.nodeType == minidom.Node.ELEMENT_NODE:
                    item = {}
                    items.append(item)
                    FileUtil.fill_xml_element(child, item)
        else:
            item = {}
            parent[parent_name] = item
            if element.attributes:
                for i in range(element.attributes.length):
                    attr: minidom.Attr = element.attributes.item(i)
                    item[attr.name] = attr.value

            child: minidom.Node
            for child in element.childNodes:
                if child.nodeType == minidom.Node.ELEMENT_NODE:
                    FileUtil.fill_xml_element(child, item)
